﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using SimpleWeather.BusinessEntities;
using SimpleWeather.WeatherService;
using StyleMVVM.Configuration;
using StyleMVVM.Data;
using StyleMVVM.Data.Collections;
using StyleMVVM.Data.Transform;
using StyleMVVM.DependencyInjection;
using StyleMVVM.LanguageExtensions;
using StyleMVVM.Notification.Tile;
using StyleMVVM.Utilities;
using Windows.Devices.Geolocation;
using Windows.UI.Xaml;

namespace SimpleWeather.DataControllers
{
	[Shared]
	[Export(typeof (IWeatherServiceController))]
	public class WeatherServiceController : NotifyObject, IWeatherServiceController
	{
		private bool? isLocalEnabled = null;
		private bool? isBostonEnabled = null;
		private static object lockObject = new object();
		private Dictionary<short, GenericWeatherDescription> descriptions;
		private ObservableCollection<CityWeather> weathers;
		private DispatcherTimer timer;

		public WeatherServiceController()
		{
			timer = new DispatcherTimer();

			// Update once an hour
			timer.Interval = new TimeSpan(1,0,0);
			timer.Tick += (sender, o) => PopulateWeather();
		}

		[Import]
		public IDependencyInjectionContainer Container { get; set; }

		[Import]
		public ITransformService TransformService { get; set; }

		[Import]
		public IConfigurationService ConfigurationService { get; set; }

		public bool? IsLocalEnabled
		{
			get { return isLocalEnabled; }
			set
			{
				if (isLocalEnabled != value)
				{
					var oldValue = isLocalEnabled;

					isLocalEnabled = value;

					if (isLocalEnabled.HasValue)
					{
						ConfigurationService.SetValue("IsLocalEnabled", isLocalEnabled.Value);
					}

					if(oldValue.HasValue)
					{
						PopulateWeather();
					}
				}
			}
		}

		public bool? IsBostonEnabled
		{
			get { return isBostonEnabled; }
			set
			{
				if (isBostonEnabled != value)
				{
					var oldValue = isBostonEnabled;

					isBostonEnabled = value;

					if (isBostonEnabled.HasValue)
					{
						ConfigurationService.SetValue("IsBostonEnabled", isBostonEnabled.Value);
					}

					if(oldValue.HasValue)
					{
						PopulateWeather();
					}
				}
			}
		}

		public async Task<ObservableCollection<CityWeather>> GetWeatherForAllCities()
		{
			if (weathers == null)
			{
				weathers = new ObservableCollection<CityWeather>();

				await PopulateWeather();
			}

			return weathers;
		}

		private async Task PopulateWeather()
		{
			try
			{
				if(timer.IsEnabled)
				{
					timer.Stop();
				}

				weathers.Clear();

				await GetWeatherInfo();

				// Geolocator is used to get current position. 
				// This call causes problems in Design Mode 
				if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled && await GetIsLocalEnabled())
				{
					try
					{
						CityWeather currentWeather =
							await getCityWeatherForCurrentLocation();

						weathers.Add(currentWeather);

					}
					catch (Exception exp)
					{
						Log.Error("Exception thrown while getting local weather data", exp);
					}
				}

				if (await GetIsBostonEnabled())
				{
					try
					{
						CityWeather boston =
							await getCityWeatherByPostalCode("02101");

						weathers.Add(boston);
					}
					catch (Exception exp)
					{
						Log.Error("Exception thrown while getting boston weather data", exp);
					}
				}

				UpdateTile();

				timer.Start();
			}
			catch (Exception exp)
			{
				Log.Error("Exception thrown while getting weather data", exp);
			}
		}

		public string GetUniqueForecastID(CompleteDayForecast forecast)
		{
			CityWeather cityWeather = forecast.GetRoot() as CityWeather;

			if (cityWeather != null)
			{
				return cityWeather.PostalCode + "|" + forecast.Date.Date;
			}

			return "";
		}

		public CompleteDayForecast GetForecast(string uniqueID)
		{
			CompleteDayForecast returnValue = null;

			string[] forecastInfo = uniqueID.Split('|');

			if (forecastInfo.Length == 2)
			{
				string postalCode = forecastInfo[0];
				string dateString = forecastInfo[1];
				DateTime outTime;

				if (DateTime.TryParse(dateString, out outTime))
				{
					foreach (CityWeather cityWeather in weathers)
					{
						if (cityWeather.PostalCode == postalCode)
						{
							if (cityWeather.ExtendedForecast != null &&
								  cityWeather.ExtendedForecast.ForecastResult != null)
							{
								foreach (CompleteDayForecast completeDayForecast in cityWeather.ExtendedForecast.ForecastResult)
								{
									if (completeDayForecast.Date.Date == outTime)
									{
										returnValue = completeDayForecast;
										break;
									}
								}
							}
							break;
						}
					}
				}
			}

			return returnValue;
		}

		private void UpdateTile()
		{
			var tileService = Container.Locate<ITileService>();

			tileService.Clear();

			tileService.EnableNotificationQueue(true);

			foreach (CityWeather cityWeather in weathers)
			{
				var wideTile = tileService.CreateTileWideSmallImageAndText04();
				var squareTile = tileService.CreateTileSquareText01();

				squareTile.TextField1 = cityWeather.CityDisplayName;
				wideTile.TextField1 = cityWeather.CityDisplayName;
				
				if(cityWeather.ExtendedForecast.ForecastResult.Count > 0)
				{
					string temps = "Hi/Lo: " + cityWeather.ExtendedForecast.ForecastResult[0].Temperatures.DaytimeHigh + "/" +
												 cityWeather.ExtendedForecast.ForecastResult[0].Temperatures.MorningLow;

					squareTile.TextField2 = temps;

					wideTile.Image1 = cityWeather.ExtendedForecast.ForecastResult[0].ImageURL;
					wideTile.TextField2 = temps;
				}

				wideTile.RectangleUpdate = squareTile;

				tileService.Update(wideTile);
			}
		}

		private async Task<bool> GetIsLocalEnabled()
		{
			if(!IsLocalEnabled.HasValue)
			{
				IsLocalEnabled =  await ConfigurationService.GetValue("IsLocalEnabled", true);
			}

			return IsLocalEnabled.Value;
		}

		private async Task<bool> GetIsBostonEnabled()
		{
			if(!IsBostonEnabled.HasValue)
			{
				IsBostonEnabled = await ConfigurationService.GetValue("IsBostonEnabled", true);
			}

			return IsBostonEnabled.Value;
		}

		private async Task<CityWeather> getCityWeatherForCurrentLocation()
		{
			return await getCityWeatherByPostalCode(await getLocalPostalCode());
		}

		private async Task<CityWeather> getCityWeatherByPostalCode(string postalCode)
		{
			CityWeather returnValue = new CityWeather();
			WeatherSoap client = Container.Locate<WeatherSoap>();

			WeatherReturn weatherReturn = await client.GetCityWeatherByZIPAsync(postalCode);

			ForecastReturn forecastReturn = await client.GetCityForecastByZIPAsync(postalCode);

			returnValue.CurrentWeather =
				TransformService.Transform<WeatherReturn, CurrentWeatherInfo>(weatherReturn);

			returnValue.ExtendedForecast =
				TransformService.Transform<ForecastReturn, ExtendedForecast>(forecastReturn);

			GenericWeatherDescription weatherDescription;

			if (descriptions.TryGetValue(returnValue.CurrentWeather.WeatherID, out weatherDescription))
			{
				returnValue.CurrentWeather.CurrentDescription = weatherDescription;
			}

			if (returnValue.ExtendedForecast.ForecastResult != null)
			{
				ComplexList<CompleteDayForecast> forecasts =
					returnValue.ExtendedForecast.ForecastResult;

				foreach (var forecast in returnValue.ExtendedForecast.ForecastResult)
				{
					if (descriptions.TryGetValue(forecast.WeatherID, out weatherDescription))
					{
						forecast.WeatherDescription = weatherDescription;
					}
				}
			}

			returnValue.PostalCode = postalCode;

			return returnValue;
		}

		private async Task GetWeatherInfo()
		{
			WeatherSoap client = Container.Locate<WeatherSoap>();

			var weatherInfo = await
				client.GetWeatherInformationAsync(new GetWeatherInformationRequest());

			List<GenericWeatherDescription> returnList =
				TransformService.Transform<WeatherDescription[], List<GenericWeatherDescription>>(
					weatherInfo.GetWeatherInformationResult);

			Dictionary<short, GenericWeatherDescription> newDescriptions
				 = new Dictionary<short, GenericWeatherDescription>();

			foreach (GenericWeatherDescription descirption in returnList)
			{
				newDescriptions[descirption.WeatherID] = descirption;
			}

			descriptions = newDescriptions;
		}

		private async Task<string> getLocalPostalCode()
		{
			Geolocator Locator = new Geolocator();
			Geoposition Position = await Locator.GetGeopositionAsync();

			if (string.IsNullOrEmpty(Position.CivicAddress.PostalCode))
			{
				HttpClient Client = new HttpClient();
				string Result = await Client.GetStringAsync(
					string.Format("http://nominatim.openstreetmap.org/reverse?format=xml&zoom=18&lat={0}&lon={1}&application={2}",
									  Position.Coordinate.Latitude.ToString(CultureInfo.InvariantCulture),
									  Position.Coordinate.Longitude.ToString(CultureInfo.InvariantCulture), "StyleMVVM Sample App"));

				XDocument ResultDocument = XDocument.Parse(Result);
				XElement AddressElement = ResultDocument.Root.Element("addressparts");

				XElement postalCodeElement = AddressElement.Element("postcode");

				if (postalCodeElement != null)
				{
					return postalCodeElement.Value;
				}
			}
			else
			{
				return Position.CivicAddress.PostalCode;
			}

			throw new CannotGetCurrentLocationPostalCodeException();
		}
	}
}
